/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "httpd.h"
#include "http_config.h"
#include "http_conf_globals.h"
#include "http_main.h"
#include "mod_wtcl.h"
#include "WtContext.h"
#include "WtUtil.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtMtTable.h"
#include "WtSettings.h"
#include "WtExecute.h"
#include "WtCollection.h"
#include "WtCookie.h"
#include "WtWebErrors.h"
#include "WtClientRequest.h"
#include "WtResponse.h"
#include "WtHtmlEntities.h"
#include "WtDbSession.h"
#include "WtProcSession.h"
#include "WtAppTable.h"
#include "WtTableCmds.h"
#include "WtInitCmds.h"

const char *wtVersion = "1.0";

/* Initialize the server */

void WtInitServer(server_rec *s, pool *p)
{
  ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, s,
    "Wtcl: WtInitServer (pid = %d, tid = %d).",
    getpid(), gettid());

  /*
  if ((ver = Tcl_InitStubs(interp, "8.1", 0)) == NULL) {
    ap_log_error(HERE, APLOG_ERR | APLOG_NOERRNO, s,
      "Wtcl: Tcl_InitStubs failed."); 
  } else {
    ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, r,
      "Wtcl: Tcl_InitStubs returned version \"%s\".",
      ver);
  }
  */

  Tcl_FindExecutable(ap_server_argv0);
  /* Tcl_FindExecutable("C:\\Program Files\\Tcl\\bin\\tclsh84.exe"); */

  Tcl_RegisterObjType(&WtTableType);
  Tcl_RegisterObjType(&WtClnType);
  Tcl_RegisterObjType(&WtCookieClnType);
  Tcl_RegisterObjType(&WtTableType);
  Tcl_RegisterObjType(&WtCaselessTableType);
  Tcl_RegisterObjType(&WtMtTableType);
  Tcl_RegisterObjType(&WtDbSessionType);

  Tcl_RegisterObjType(&WtProcSessionType);
  Tcl_RegisterObjType(&WtPSTableType);
  Tcl_InitObjHashTable(&wtPSTables);

  Tcl_RegisterObjType(&WtAppTableType);
  WtInitAppTableSet(&wtAppTables);
}

/* Get the current context */

WtContext *WtGetContext()
{
  WtThreadStorage *ts = WtGetThreadStorage();
  if (!ts->context) {
    ts->context = (WtContext *)ckalloc(sizeof(WtContext));
    memset(ts->context, '\0', sizeof(WtContext));
  }
  return ts->context;
}

/* Process a request */

int WtProcessRequest(request_rec *r)
{
  WtContext *w;
  int ok = 1;
  WtDispatch disp;

  Tcl_GetTime(&disp.startTime);

  ap_soft_timeout("Wtcl request", r);

  disp.status = OK;
  disp.terminate = 0;

  ap_log_rerror(HERE, APLOG_DEBUG | APLOG_NOERRNO, r,
    "Wtcl: WtProcessRequest (r = 0x%x, PID = %d, TID = %d).",
    r, getpid(), gettid());

  w = WtGetContext();

  if (!w) {
    WtPrintInitError(HERE, APLOG_ERR | APLOG_NOERRNO, r, 1,
      "Failed to create Wtcl context.");
    ok = 0;
  } else {
    if (!WtInitRequest(w, r, &disp)) {
      ok = 0;
    } else {
      ap_chdir_file(r->filename);
      if (r->finfo.st_mode == 0) {
        w->web->clientResponse.status = HTTP_NOT_FOUND;
      } else if (!WtExecuteRequest(w, r->filename)) {
        ok = 0;
      }
    }

    if (!WtEndRequest(w, r, &disp)) {
      ok = 0;
    }
  }

  if (disp.terminate) {
    WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
      "Wtcl: WtProcessRequest: Terminating worker process.");
    WtDeleteContext();
#ifndef WIN32
    ap_child_terminate(r);
#endif /* !WIN32 */
  }

  ap_kill_timeout(r);

  return disp.status;
}

/* Initialize the request */

int WtInitRequest(WtContext *w, request_rec *r, WtDispatch *disp)
{
  int ok = 1, reuseInterp, boolVal;
  Tcl_Obj *settings, *serverConf, *dirConf, *namesp, *objv[4];
  WtWebContext *web;

  /* Init worker */

  w->apServer = r->server;
  w->apReq = r;
  w->requestCount++;

  if (!w->webChannel) {
    w->webChannel = Tcl_CreateChannel(&WtOutChanType, "web", w, TCL_WRITABLE);
    Tcl_SetStdChannel(w->webChannel, TCL_STDOUT);
    Tcl_RegisterChannel(NULL, w->webChannel);  /* Hold reference */
  }

  /* Request settings */

  settings = WtNewTableObj();
  Tcl_IncrRefCount(settings);
  WtInitDefaultContextSettings(w);
  WtUpdateTable(settings, w->defaultCtxSettings);

  if (serverConf = WtGetServerConf(r->server)) {
    WtUpdateTable(settings, serverConf);
  }
  if (dirConf = WtGetDirConf(r)) {
    WtUpdateTable(settings, dirConf);
  }

  /* Get the web context */

  reuseInterp = WtTableGetBoolFromStr(settings, "server.reuseInterp", 1);
  if (reuseInterp) {
    if (!w->persistentWebContext) {
      w->persistentWebContext = WtCreateWebContext(1);
    }
    web = w->web = w->persistentWebContext;
  } else {
    if (!w->transientWebContext) {
      w->transientWebContext = WtCreateWebContext(0);
    }
    web = w->web = w->transientWebContext;
  }

  if (!web) {
    WtPrintInitError(HERE, APLOG_ERR | APLOG_NOERRNO, r, 1,
      "Failed to create web context.");
    ok = 0;
  }

  /* Init the web context */

  if (ok) {
    web->apReq = r;
    web->ctxRequestCount++;
    web->requestStartTime = disp->startTime;
    web->clientResponse.status = OK;
    WtSetObj(&web->taskSettings, settings);

    namesp = WtTableGetObjFromStr(web->taskSettings,
      "taskNamespace", NULL);
    if (namesp) {
      WtSetObj(&w->web->taskNamespace, namesp);
    }

    if (!WtInitWebContext(w, 1)) {
      ok = 0;
    }
  }

  /* Start the request */

  if (ok) {
    if (!Tcl_CreateObjCommand(w->web->interp,
        "::wt::internal::startRequest",
        WtStartRequestCmd, NULL, NULL)) {
      ok = 0;
    } else {
      objv[0] = WtNewString("namespace");
      objv[1] = WtNewString("eval");
      objv[2] = w->web->taskNamespace;
      objv[3] = WtNewString("::wt::internal::startRequest");

      if (WtEvalIncr(w->web->interp, 4, objv, TCL_EVAL_DIRECT) != TCL_OK ||
          !WtGetBoolResult(w->web->interp, &boolVal)) {
        WtInterpError(HERE, w, w->web->interp);
        ok = 0;
      } else if (!boolVal) {
        ok = 0;
      }
    }
  }

  return ok;
}

/* Create a new web context */

WtWebContext *WtCreateWebContext(int isPersistent)
{
  WtWebContext *wc = (WtWebContext *)ckalloc(sizeof(WtWebContext));
  memset(wc, '\0', sizeof(WtWebContext));
  wc->isPersistent = isPersistent;
  return wc;
}

/* Web context initialization process */

int WtInitWebContext(WtContext *w, int initRequest)
{
  int ok = 1;
  WtWebContext *web = w->web;

  if (!web->aInitDone) {
    web->errorState = 0;
    WtSetObj(&web->notices, Tcl_NewListObj(0, NULL));
    WtSetObj(&web->taskNamespace, WtNewString("::wt::task::"));
    WtSetObj(&web->ctxEventHandlers, WtNewTableObj());
    web->aInitDone = 1;
  }

  if (!web->usedNamespaces) {
    WtSetObj(&web->usedNamespaces, WtNewTableObj());
  }

  if (!WtCreateInterp(w, initRequest) ||
      !WtInitInterp(w, initRequest) ||
      !WtInitPackages(w, initRequest)) {
    ok = 0;
  }

  return ok;
}

/* Create the interpreter */

int WtCreateInterp(WtContext *w, int initRequest)
{
  int ok = 1;
  Tcl_Interp *interp;

  if (w->web->interp) {
    return 1;
  }

  interp = w->web->interp = Tcl_CreateInterp();

  if (!interp) {
    WtLog(HERE, APLOG_ERR | APLOG_NOERRNO, w, "Wtcl: Tcl_CreateInterp failed.");
    ok = 0;
  } else {
    w->web->interpIsNew = 1;
    w->web->interpCreationTime = time(NULL);
    w->web->interpRequestCount = 0;
    Tcl_SetAssocData(interp, "wt::context", NULL, w);
    // Tcl_RegisterChannel(interp, w->webChannel);

    if (Tcl_Init(interp) != TCL_OK) {
      WtInterpError(HERE, w, interp);
      ok = 0;
    }
  }

  if (!ok && interp) {
    // Tcl_UnregisterChannel(interp, w->webChannel);
    Tcl_DeleteInterp(interp);
    w->web->interp = NULL;
  }

  return ok;
}

/* Initialize the interp */

int WtInitInterp(WtContext *w, int initRequest)
{
  int ok, boolVal;
  Tcl_Obj *objv[5];

  if (!Tcl_CreateObjCommand(w->web->interp,
      "::wt::internal::initInterp", WtInitInterpCmd, NULL, NULL)) {
    ok = 0;
  } else {
    objv[0] = WtNewString("namespace");
    objv[1] = WtNewString("eval");
    objv[2] = w->web->taskNamespace;
    objv[3] = WtNewString("::wt::internal::initInterp");
    objv[4] = WtNewBool(initRequest);

    if (WtEvalIncr(w->web->interp, 5, objv, TCL_EVAL_DIRECT) != TCL_OK ||
        !WtGetBoolResult(w->web->interp, &boolVal)) {
      WtInterpError(HERE, w, w->web->interp);
      ok = 0;
    } else if (!boolVal) {
      ok = 0;
    }
  }

  return ok;
}

/* Init packages and imports */

int WtInitPackages(WtContext *w, int initRequest)
{
  int ok, boolVal;
  Tcl_Obj *objv[4];

  if (!Tcl_CreateObjCommand(w->web->interp,
      "::wt::internal::initPackages", WtInitPackagesCmd, NULL, NULL)) {
    ok = 0;
  } else {
    objv[0] = WtNewString("namespace");
    objv[1] = WtNewString("eval");
    objv[2] = w->web->taskNamespace;
    objv[3] = WtNewString("::wt::internal::initPackages");

    if (WtEvalIncr(w->web->interp, 4, objv, TCL_EVAL_DIRECT) != TCL_OK ||
        !WtGetBoolResult(w->web->interp, &boolVal)) {
      WtInterpError(HERE, w, w->web->interp);
      ok = 0;
    } else if (!boolVal) {
      ok = 0;
    }
  }

  return ok;
}

int WtInitTaskObjectsB(WtContext *w)
{
  int ok = 1;
  WtWebContext *web = w->web;
  Tcl_Obj *str;

  if (w->web->bInitDone) {
    return 1;
  }

  web->importCommands = 1;
  web->pageIsStarted = 0;
  web->readEnvStatus = WT_STATUS_NONE;

  WtSetObj(&web->serverVars, WtNewTableObj());

  /* Page loader settings */

  str = WtNewString(".");
  WtSetObj(&web->loader.includeSearchPath, Tcl_NewListObj(1, &str));
  WtSetObj(&web->loader.includeNamespace, WtNewString(NULL));
  web->loader.addHeaderTrailer = 0;
  WtSetObj(&web->loader.includeHeader, WtNewString(NULL));
  WtSetObj(&web->loader.includeTrailer, WtNewString(NULL));
  WtSetObj(&web->loader.scriptStartString, WtNewString("<?"));
  WtSetObj(&web->loader.scriptEndString, WtNewString("?>"));
  web->loader.interpolateText = 1;
  WtSetObj(&web->loader.textHandler, WtNewString(NULL));
  WtSetObj(&web->loader.exprHandler, WtNewString(NULL));
  web->loader.pageIndex = -1;
  WtSetObj(&web->loader.pageStack, Tcl_NewListObj(0, NULL));

  /* Client request */

  WtSetObj(&web->clientRequest.headers, WtNewCaselessTableObj());
  web->clientRequest.parseStatus = WT_STATUS_NONE;
  web->clientRequest.readRequestStatus = WT_STATUS_NONE;
  web->clientRequest.apWeb = NULL;
  WtSetObj(&web->clientRequest.queryParams, WtNewTableObj());
  WtSetObj(&web->clientRequest.postParams, WtNewTableObj());
  web->clientRequest.readCookiesStatus = WT_STATUS_NONE;
  web->clientRequest.apCookies = NULL;
  WtSetObj(&web->clientRequest.cookies, WtNewTableObj());
  WtSetObj(&web->clientRequest.uploads, WtNewTableObj());
  WtSetObj(&web->clientRequest.uploadHandler, WtNewString(NULL));
  WtSetObj(&web->clientRequest.params, WtNewTableObj());

  /* Client response */

  WtSetObj(&web->clientResponse.contentType, WtNewString(NULL));
  WtSetObj(&web->clientResponse.headers, WtNewCaselessTableObj());
  WtSetObj(&web->clientResponse.successHeaders, WtNewCaselessTableObj());
  web->clientResponse.headersStatus = WT_STATUS_NONE;
  WtSetObj(&web->clientResponse.cookies, WtNewCookieClnObj(NULL));
  web->clientResponse.status = OK;

  web->deleteInterp = 0;
  WtInitSession(&web->session);
  WtSetObj(&web->dataSourceHandler, WtNewString(NULL));

  if (ok) {
    web->bInitDone = 1;
  }

  return ok;
}

/* End the request */

int WtEndRequest(WtContext *w, request_rec *r, WtDispatch *disp)
{
  int ok = 1, boolVal, closeError = 0;
  Tcl_Obj *objv[4];

  /* Run the exit handlers */

  if (w->web && w->web->interp) {
    if (!Tcl_CreateObjCommand(w->web->interp,
        "::wt::internal::endRequest", WtEndRequestCmd, NULL, NULL)) {
      ok = 0;
    } else {
      objv[0] = WtNewString("namespace");
      objv[1] = WtNewString("eval");
      objv[2] = w->web->taskNamespace;
      objv[3] = WtNewString("::wt::internal::endRequest");

      if (WtEvalIncr(w->web->interp, 4, objv, 0) != TCL_OK ||
          !WtGetBoolResult(w->web->interp, &boolVal)) {
        WtInterpError(HERE, w, w->web->interp);
        w->web->terminate = 1;
        ok = 0;
      } else if (!boolVal) {
        ok = 0;
      }
    }
  }

  /* Flush output */

  if (w->web) {
    if (!w->webChannel || Tcl_Flush(w->webChannel) != TCL_OK) {
      WtLog(HERE, APLOG_ERR | APLOG_NOERRNO, w, "Tcl_Flush failed.");
      ok = 0;
    }
  }

  /* Prepare the next task */

  if (w->web) {
    if (disp->terminate) {
      w->web->terminate = disp->terminate;
    }

    if (!w->web->terminate) {
      w->web->interpIsNew = 0;

      /* Recycle the interp */

      if (!w->web->isPersistent || w->web->deleteInterp) {
        if (!WtRecreateInterp(w, disp)) {
          ok = 0;
        }
      } else if (!WtReuseInterp(w)) {
        ok = 0;
      }

      /* Print errors */

      if (!ok || w->web->errorState) {
        WtHandleErrorsNs(w);
      }

      /* Send headers */

      if (!WtSendHeaders(w, w->web->apReq, w->web->interp)) {
        ok = 0;
      }

      /* Final flush */

      if (!w->webChannel || Tcl_Flush(w->webChannel) != TCL_OK) {
        WtLog(HERE, APLOG_ERR | APLOG_NOERRNO, w, "Tcl_Flush failed.");
        closeError = 1;
        ok = 0;
      }
    }

    disp->status = w->web->clientResponse.status;
    if (w->web->terminate) {
      disp->terminate = w->web->terminate;
    }

    /* Clear the internal task state */

    if (!disp->terminate && !WtClearTaskObjects(w)) {
      closeError = 1;
      ok = 0;
    }
  } else {
    ap_rflush(r);
  }

  return ok;
}

/* Re-create the interp */

int WtRecreateInterp(WtContext *w, WtDispatch *disp)
{
  int ok = 1;

  if (w->web && w->web->interp && !WtDeleteInterp(w)) {
    ok = 0;
  }

  if (ok && !WtInitWebContext(w, 0)) {
    ok = 0;
  }

  return ok;
}

/* Delete any state dependent on the interp */

int WtDeleteInterp(WtContext *w)
{
  if (w->web) {
    if (w->web->interp) {
      /*
      if (w->webChannel) {
        Tcl_UnregisterChannel(w->web->interp, w->webChannel);
      }
      */
      if (!Tcl_InterpDeleted(w->web->interp)) {
        Tcl_DeleteInterp(w->web->interp);
      }
      w->web->interp = NULL;
    }

    WtDecr(w->web->usedNamespaces);
    w->web->usedNamespaces = NULL;
  }

  return 1;
}

/* Post-request cleanup */

int WtReuseInterp(WtContext *w)
{
  int ok = 1, deleteNs = 0;
  Tcl_Obj *exists, *cond, *del, *ifCmd[3];
  Tcl_Interp *interp;
  char *nsStr, *p;

  if (w->web->interp) {
    interp = w->web->interp;

    /* Make sure the namespace isn't empty */

    if (Tcl_GetCharLength(w->web->taskNamespace)) {
      nsStr = Tcl_GetString(w->web->taskNamespace);
      for (p = nsStr; *p == ':'; p++) ;
      deleteNs = *p != '\0';
    }

    if (deleteNs) {
      /* XXX: Use assertion checking append functions */

      exists = Tcl_NewListObj(0, NULL);
      Tcl_ListObjAppendElement(interp, exists, WtNewString("namespace"));
      Tcl_ListObjAppendElement(interp, exists, WtNewString("exists"));
      Tcl_ListObjAppendElement(interp, exists, w->web->taskNamespace);

      cond = WtNewString("[");
      Tcl_AppendObjToObj(cond, exists);
      Tcl_AppendToObj(cond, "]", 1);

      del = Tcl_NewListObj(0, NULL);
      Tcl_ListObjAppendElement(interp, del, WtNewString("namespace"));
      Tcl_ListObjAppendElement(interp, del, WtNewString("delete"));
      Tcl_ListObjAppendElement(interp, del, w->web->taskNamespace);

      ifCmd[0] = WtNewString("if");
      ifCmd[1] = cond;
      ifCmd[2] = del;

      if (WtEvalIncr(w->web->interp, 3, ifCmd, 0) != TCL_OK) {
        WtInterpError(HERE, w, w->web->interp);
        ok = 0;
      } else if (!WtOwnTable(&w->web->usedNamespaces, w->web->interp)) {
        ok = 0;
      } else {
        WtTableRemove(w->web->usedNamespaces, w->web->taskNamespace);
      }
    }
  }

  if (ok && !WtInitWebContext(w, 0)) {
    ok = 0;
  }

  return ok;
}

/* Clear objects used in this request */

int WtClearTaskObjects(WtContext *w)
{
  int ok = 1;
  WtWebContext *web = w->web;

  web->bInitDone = 0;

  web->apReq = NULL;
  web->requestStartTime.sec = 0;
  web->requestStartTime.usec = 0;
  web->errorState = 0;
  WtSetObj(&web->notices, Tcl_NewListObj(0, NULL));
  WtSetObj(&web->taskSettings, WtNewTableObj());
  WtSetObj(&web->taskNamespace, WtNewString("::wt::task::"));
  WtSetObj(&web->ctxEventHandlers, WtNewTableObj());

  if (web->clientRequest.uploadHandlerData) {
    Tcl_DecrRefCount(web->clientRequest.uploadHandlerData);
    web->clientRequest.uploadHandlerData = NULL;
  }

  if (!WtDeleteErrors(w, web->interp)) {
    ok = 0;
  }

  web->terminate = 0;

  WtDeleteSession(&web->session);

  if (!WtInitTaskObjectsB(w)) {
    ok = 0;
  }

  if (web->interp) {
    Tcl_ResetResult(w->web->interp);
  }

  return ok;
}

/* Delete worker state prior to shutdown */

int WtDeleteContext()
{
  int ok = 1;
  WtThreadStorage *ts = WtGetThreadStorage();
  WtContext *w = NULL;

  if (ts->context) {
    w = ts->context;

    if (w->persistentWebContext) {
      w->web = w->persistentWebContext;
      if (!WtDeleteWebContext(w)) {
        ok = 0;
      }
      ckfree((char *)w->web);
      w->persistentWebContext = NULL;
    }

    if (w->transientWebContext) {
      w->web = w->transientWebContext;
      if (WtDeleteWebContext(w)) {
        ok = 0;
      }
      ckfree((char *)w->web);
      w->transientWebContext = NULL;
    }

    if (w->webChannel) {
      Tcl_UnregisterChannel(NULL, w->webChannel);
    }

    w->web = NULL;

    if (w->optionCodes) {
      Tcl_DeleteHashTable(w->optionCodes);
      ckfree((char *)w->optionCodes);
      w->optionCodes = NULL;
    }

    if (w->optionKeys) {
      WtDeleteOptionKeys(w->optionKeys);
      Tcl_DeleteHashTable(w->optionKeys);
      ckfree((char *)w->optionKeys);
      w->optionKeys = NULL;
    }

    if (w->defaultCtxSettings) {
      Tcl_DecrRefCount(w->defaultCtxSettings);
      w->defaultCtxSettings = NULL;
    }

    ckfree((char *)ts->context);
    ts->context = NULL;
  }

  WtDeleteHtmlEntities();

  return ok;
}

/* Delete the web context prior to shutdown */

int WtDeleteWebContext(WtContext *w)
{
  int ok = 1;
  WtWebContext *web;

  if (!WtDeleteInterp(w)) {
    ok = 0;
  }

  if (w->web) {
    web = w->web;
    WtDecr(web->notices);
    WtDecr(web->taskSettings);
    WtDecr(web->taskNamespace);
    WtDecr(web->ctxEventHandlers);
    WtDecr(web->serverVars);
    WtDecr(web->loader.includeSearchPath);
    WtDecr(web->loader.includeNamespace);
    WtDecr(web->loader.includeHeader);
    WtDecr(web->loader.includeTrailer);
    WtDecr(web->loader.scriptStartString);
    WtDecr(web->loader.scriptEndString);
    WtDecr(web->loader.textHandler);
    WtDecr(web->loader.exprHandler);
    WtDecr(web->loader.pageStack);

    WtDecr(web->clientRequest.headers);
    WtDecr(web->clientRequest.queryParams);
    WtDecr(web->clientRequest.postParams);
    WtDecr(web->clientRequest.cookies);
    WtDecr(web->clientRequest.uploads);
    WtDecr(web->clientRequest.uploadHandler);
    WtDecr(web->clientRequest.uploadHandlerData);

    WtDecr(web->clientResponse.headers);
    WtDecr(web->clientResponse.successHeaders);
    WtDecr(web->clientResponse.cookies);

    if (!WtDeleteErrors(w, web->interp)) {
      ok = 0;
    }

    WtDeleteSession(&web->session);

    WtDecr(web->dataSourceHandler);
  }

  return ok;
}

void WtEndServer(server_rec *s, pool *p)
{
  ap_log_error(HERE, APLOG_DEBUG | APLOG_NOERRNO, s,
    "Wtcl: WtEndServer (pid = %d, tid = %d).",
    getpid(), gettid());

  Tcl_DeleteHashTable(&wtPSTables);
  Tcl_MutexFinalize(&wtPSTablesLock);
}

Tcl_Obj *WtGetServerConf(server_rec *s)
{
  Tcl_Obj *conf = (Tcl_Obj *)ap_get_module_config(s->module_config, &wtcl_module);
  if (conf && !WtConvertToTable(conf, NULL)) {
    conf = NULL;
  }
  return conf;
}

Tcl_Obj *WtGetDirConf(request_rec *r)
{
  Tcl_Obj *conf = (Tcl_Obj *)ap_get_module_config(r->per_dir_config, &wtcl_module);
  if (conf && !WtConvertToTable(conf, NULL)) {
    conf = NULL;
  }
  return conf;
}

WtContext *WtGetAssocContext(Tcl_Interp *interp)
{
  return (WtContext *)Tcl_GetAssocData(interp, "wt::context", NULL);
}
